using System;
using System.Collections.Generic;

using Heaven.Graphics;
using Heaven.Engine.Animation;
using Heaven.Mathematics;
using Heaven.Texturing;

namespace Heaven.Engine
{
    /// <summary>
    /// This is a class of HLSL shader, 
    /// considering as a Technique
    /// </summary>
    public class Technique
    {
        #region Delegates

        /// <summary>
        /// Delegate to store different parameters setter in one presentation
        /// </summary>
        delegate void SetCameraParameterDelegate(IDevice device, Shader shader, Camera camera, string name);

        /// <summary>
        /// Delegate to store different parameters setter in one presentation
        /// </summary>
        delegate void SetNodeParameterDelegate(IDevice device, Shader shader, Visual node, Camera camera, string name);

        #endregion

        #region Events

        // Weak event
        WeakMulticastDelegate invalidated = new WeakMulticastDelegate();

        /// <summary>
        /// Occurs when the technique or one of its parts have been changed
        /// </summary>
        public event EventHandler Invalidated
        {
            add { invalidated.Add(value); }
            remove { invalidated.Remove(value); }
        }

        #endregion
        
        #region Fields

        // Properties of this technique
        PropertyCollection properties = new PropertyCollection();

        /// <summary>
        /// HLSL code
        /// </summary>
        protected Shader shader = null;
        // Render passes
        RendererCollection passes = new RendererCollection();

        // Camera Standard Annotations
        List<SetCameraParameterDelegate> cameraAnnotationsSetter = null;
        List<string> cameraAnnotationsNames = null;

        // Node Standard Annotations
        List<SetNodeParameterDelegate> nodeAnnotationsSetter = null;
        List<string> nodeAnnotationsNames = null;

        #endregion

        #region Properties

        public RendererCollection Passes
        {
            get { return passes; }
            set
            {
                passes.Invalidated -= new EventHandler(OnPassesInvalidated);
                passes = value;
                passes.Invalidated += new EventHandler(OnPassesInvalidated);
                Invalidate();
            }
        }

        /// <summary>
        /// Shader object
        /// </summary>
        public Shader Shader
        {
            get { return shader; }
        }

        /// <summary>
        /// Gets or sets properties of the technique
        /// </summary>
        public PropertyCollection Properties
        {
            get { return properties; }
            set 
            {
                properties.Invalidated -= new EventHandler(OnPropertiesInvalidated);
                properties = value;
                properties.Invalidated += new EventHandler(OnPropertiesInvalidated);
                Invalidate();
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="hlsl">HLSL source code</param>
        public Technique(string hlsl)
        {
            this.shader = new Shader(hlsl);
            this.properties.Invalidated += new EventHandler(OnPropertiesInvalidated);
            this.passes.Invalidated += new EventHandler(OnPassesInvalidated);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        protected Technique()
        {            
            this.properties.Invalidated += new EventHandler(OnPropertiesInvalidated);
            this.passes.Invalidated += new EventHandler(OnPassesInvalidated);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates object
        /// </summary>
        public virtual void Invalidate()
        {
            invalidated.Invoke(this, EventArgs.Empty);
        }

        #endregion

        #region Event Handles

        void OnPropertiesInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        void OnPassesInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        #endregion

        #region Camera Standard Annotations

        /// <summary>
        /// Sets SAS parameters which are refered to camera
        /// </summary>
        /// <param name="device">Graphics device</param>
        /// <param name="camera">Camera</param>
        internal void SetCameraStandardAnnotations(IDevice device, Camera camera)
        {
            #region Generate parameter list

            if (cameraAnnotationsSetter == null)
            {
                cameraAnnotationsSetter = new List<SetCameraParameterDelegate>();
                cameraAnnotationsNames = new List<string>();

                string name = null;

                name = device.GetParameterBySemantic(shader, "VIEWPROJECTION");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewProjectionSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWINVERSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "PROJECTIONINVERSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ProjectionInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWPROJECTIONINVERSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewProjectionInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWTRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "PROJECTIONTRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ProjectionTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWPROJECTIONTRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewProjectionTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWINVERSETRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewInverseTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "PROJECTIONINVERSETRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ProjectionInverseTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "VIEWPROJECTIONINVERSETRANSPOSE");
                if (name != null)
                {
                    cameraAnnotationsNames.Add(name);
                    cameraAnnotationsSetter.Add(ViewProjectionInverseTransposeSetup);
                }
            }

            #endregion

            // Set standard parameters
            for (int i = 0; i < cameraAnnotationsSetter.Count; i++)
            {
                cameraAnnotationsSetter[i].Invoke(device, shader, camera, cameraAnnotationsNames[i]);
            }
        }

        static void ViewProjectionSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewProjection);
        }

        static void ViewInverseSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewInverse);
        }

        static void ProjectionInverseSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ProjectionInverse);
        }

        static void ViewProjectionInverseSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewProjectionInverse);
        }

        static void ViewTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewTranspose);
        }

        static void ProjectionTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ProjectionTranspose);
        }

        static void ViewProjectionTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewProjectionTranspose);
        }

        static void ViewInverseTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewInverseTranspose);
        }

        static void ProjectionInverseTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ProjectionInverseTranspose);
        }

        static void ViewProjectionInverseTransposeSetup(IDevice device, Shader shader, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, camera.ViewProjectionInverseTranspose);
        }

        #endregion

        #region Node Standard Annotations

        /// <summary>
        /// Sets SAS parameters which are refered to camera
        /// </summary>
        /// <param name="device">Graphics device</param>
        /// <param name="camera">Camera</param>
        internal void SetNodeStandardAnnotations(IDevice device, Camera camera, Visual visual)
        {
            #region Generate parameter list

            if (nodeAnnotationsSetter == null)
            {
                nodeAnnotationsSetter = new List<SetNodeParameterDelegate>();
                nodeAnnotationsNames = new List<string>();

                string name = null;

                name = device.GetParameterBySemantic(shader, "WORLD");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEW");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewSetup);
                }


                name = device.GetParameterBySemantic(shader, "WORLDVIEWPROJECTION");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewProjectionSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDINVERSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWINVERSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWPROJECTIONINVERSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewProjectionInverseSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDTRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWTRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWPROJECTIONTRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewProjectionTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDINVERSETRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldInverseTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWINVERSETRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewInverseTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "WORLDVIEWPROJECTIONINVERSETRANSPOSE");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(WorldViewProjectionInverseTransposeSetup);
                }

                name = device.GetParameterBySemantic(shader, "TRANSFORM");
                if (name != null)
                {
                    nodeAnnotationsNames.Add(name);
                    nodeAnnotationsSetter.Add(TransformSetup);
                }
            }

            #endregion

            // Set standard parameters
            for (int i = 0; i < nodeAnnotationsSetter.Count; i++)
            {
                nodeAnnotationsSetter[i].Invoke(device, shader, visual, camera, nodeAnnotationsNames[i]);
            }
        }

        static void WorldSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, node.Absolute);
        }

        static void WorldViewSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, node.Absolute * camera.View);
        }

        static void WorldViewProjectionSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, node.Absolute * camera.ViewProjection);
        }

        static void WorldInverseSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Inverse(node.Absolute));
        }

        static void WorldViewInverseSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Inverse(node.Absolute * camera.View));
        }

        static void WorldViewProjectionInverseSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Inverse(node.Absolute * camera.ViewProjection));
        }

        static void WorldTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(node.Absolute));
        }

        static void WorldViewTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(node.Absolute * camera.View));
        }

        static void WorldViewProjectionTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(node.Absolute * camera.ViewProjection));
        }

        static void WorldInverseTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(Matrix4.Inverse(node.Absolute)));
        }

        static void WorldViewInverseTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(Matrix4.Inverse(node.Absolute * camera.View)));
        }

        static void WorldViewProjectionInverseTransposeSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, Matrix4.Transpose(Matrix4.Inverse(node.Absolute * camera.ViewProjection)));
        }

        static void TransformSetup(IDevice device, Shader shader, Visual node, Camera camera, string name)
        {
            device.SetShaderParameter(shader, name, node.Transformation.Value);
        }
        
        #endregion

        #region Set Properties

        /// <summary>
        /// Sets the given properties
        /// </summary>
        /// <param name="device">Graphics device</param>
        /// <param name="propertyCollection">Properties</param>
        internal void SetProperties(IDevice device, PropertyCollection propertyCollection)
        {
            foreach (NamedProperty property in propertyCollection)
            {
                object value = property.Property.Value;
                if (value is double)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(double)value);
                }
                else if (value is Color)
                {
                    device.SetShaderParameter(shader, property.Name, (Color)value);
                }
                else if (value is Vector4)
                {
                    device.SetShaderParameter(shader, property.Name, (Vector4)value);
                }
                else if (value is Vector3)
                {
                    device.SetShaderParameter(shader, property.Name, (Vector3)value);
                }
                else if (value is Matrix4)
                {
                    device.SetShaderParameter(shader, property.Name, (Matrix4)value);
                }
                else if (value is Texture)
                {
                    device.SetShaderParameter(shader, property.Name, (Texture)value);
                }
                else if (value is float)
                {
                    device.SetShaderParameter(shader, property.Name, (float)value);
                }
                else if (value is decimal)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(decimal)value);
                }
                else if (value is int)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(int)value);
                }
                else if (value is long)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(long)value);
                }
            }
        }

        #endregion
    }
}
